PostgreSQL amcheck
1 背景知识
amcheck 扩展模块可以验证数据库对象的逻辑一致性,常用于索引对象是否有效。
1.1 amcheck 扩展模块原理
amcheck
扩展模块主要是查询对象中的各种常量判断对象的有效性。因为执行计划相关的操作方法的正确性是依赖于这些常量的正确性。
1、例如:验证 Btree 页面中是否按照“逻辑”顺序排列。
2、如果“逻辑”顺序混乱时,将无法正确的进行索引扫描,导致SQL 查询到错误答案。
3、例如下图,branch 节点,第二节点数据损坏,将无法进行索引扫描。
text 字段的 Btree 索引,索引的元组应该按照词典顺序排列。
1.2 amcheck 安装扩展
su - postgres
psql -U postgres -testdb
CREATE EXTENSION amcheck;
\dx+ amcheck
2 bt_index_check 函数
2.1 bt_index_check 简介
1、 bt_index_check
测试(BTREE)索引中的常量是否正确。
2、 bt_index_check
无法验证父子关系的常量。
3、heapallindexed
为 true
时将验证所有堆元组是否作为索引中的索引元组存在。
2.2 bt_index_check 加锁
1、执行 bt_index_check
函数时,需要在索引及其所属的表 AccessShareLock
。
2、这种锁模式与简单 SELECT
语句在关系上所要求的锁模式相同。
2.3 语法定义
bt_index_check(index regclass, heapallindexed boolean) returns void
函数输出的DEBUG日志信息对于某些postgresql 用户非常有用。目前有两个级别:
1、DEBUG1。
2、DEBUG2。
可以使用:SET client_min_messages = DEBUG1;
命令进行设置。
2.4 示例
这个例子中会话将会对 testdb
对10个最大的索引进行验证。
1、对于唯一索引,要求所有的索引元组都存在。
2、由于没有错误,所以被检测的索引都处于“逻辑一致”的状态。
SELECT bt_index_check(index => c.oid, heapallindexed => i.indisunique),
c.relname,
c.relpages
FROM pg_index i
JOIN pg_opclass op ON i.indclass[0] = op.oid
JOIN pg_am am ON op.opcmethod = am.oid
JOIN pg_class c ON i.indexrelid = c.oid
JOIN pg_namespace n ON c.relnamespace = n.oid
WHERE am.amname = 'btree' AND n.nspname = 'pg_catalog'
-- Don't check temp tables, which may be from another session:
AND c.relpersistence != 't'
-- Function may throw an error when this is omitted:
AND c.relkind = 'i' AND i.indisready AND i.indisvalid
ORDER BY c.relpages DESC LIMIT 10;
对于上述查询语句,请去官方文档了解具体的表字段含义。
pg_am
pg_opclass
pg_index
pg_class
pg_namespace
3 bt_index_parent_check 函数
3.1 bt_index_parent_check 简介
1、 bt_index_parent_check
函数检查不仅索引中常量,并且还会检查索引的父子常量。
2、当 heapallindexed
为 true
时,此函数将会验证索引中所有的元组都是存在的,所有页面都存在则无报错。
3、当 rootdescend
为 true
时,此函数将会从 root
页面到 left
页面进行搜索,所有页面都存在则无报错。
4、因 bt_index_parent_check
还会检查父子关系的常量,所以检查范围是 bt_index_check
检查范围的超集。检查范围更全面。
3.2 bt_index_parent_check 加锁
1、执行 bt_index_parent_check
函数时,需要在对象上加一个表集的 ShareLock
。
2、表级的 ShareLock
锁与 INSERT
,UPDATE
,DELETE
命令冲突。
3、ShareLock
也会阻止 VACUUM
清理死亡元组。
4、amcheck
函数只能由超级用户使用。
函数运行时保持锁,而不是在整个被包含的事务内保持锁。
bt_index_check
和 bt_index_parent_check
都输出关于验证过程的日志信息,在 DEBUG1
和 DEBUG2
严重性级别。
这些消息提供关于验证过程的详细信息,或许对PostgreSQL的开发人员有作用。
高级用户也许会发现这些信息很有帮助,因为它提供了额外的上下文将验证实际检测的不一致。运行:
在运行验证查询之前的交互式psql会话中,将显示有关验证进度的消息,并具有可管理级别的详细信息。
7、 bt_index_parent_check
不能再备库上执行和使用。
3.3 语法定义
bt_index_parent_check(index regclass, heapallindexed boolean, rootdescend boolean) returns void
3.4 示例
这个例子中会话将会对 testdb
对10个最大的索引进行验证。
1、对于唯一索引,要求所有的索引元组都存在,并且从root
页面都能够寻找到left
页面。
2、由于没有错误,所以被检测的索引都处于“逻辑一致”的状态。
SELECT bt_index_parent_check(index => c.oid, heapallindexed => i.indisunique,rootdescend => i.indisunique),
c.relname,
c.relpages
FROM pg_index i
JOIN pg_opclass op ON i.indclass[0] = op.oid
JOIN pg_am am ON op.opcmethod = am.oid
JOIN pg_class c ON i.indexrelid = c.oid
JOIN pg_namespace n ON c.relnamespace = n.oid
WHERE am.amname = 'btree' AND n.nspname = 'pg_catalog'
-- Don't check temp tables, which may be from another session:
AND c.relpersistence != 't'
-- Function may throw an error when this is omitted:
AND c.relkind = 'i' AND i.indisready AND i.indisvalid
ORDER BY c.relpages DESC LIMIT 10;
对于上述查询语句,请去官方文档了解具体的表字段含义。
pg_am
pg_opclass
pg_index
pg_class
pg_namespace
4 verify_heapam 函数
4.1 verify_heapam 简介
检查表的结构损坏,关系中包含无效格式的数据的页,以及逻辑损坏,页在结构上是有效的,但与数据库集群的其他部分不一致。
4.1.1 verify_heapam 参数说明
-
on_error_stop
1、如果为true
,将在发现损坏的第一个块的末尾停止。
2、默认为false
。 -
check_toast
1、如果为true
根据目标关系的TOAST表进行检查。
2、这个选项是非常耗费资源并且效率较低的。
3、默认为false
。 -
skip
1、如果不是none
,则会根据选项跳过检查某些块。
2、有效的选项为all-visible
,all-frozen
和none
。
3、默认为none
。 -
startblock
1、指定时,健康检查从指定的块开始,忽略前面所有的块。指定startblock
超出表的块的范围将会返回一个错误。
2、默认情况下,检查从第一个块开始。 -
endblock
1、指定时,健康检查在指定的块结束,忽略所有剩余的块。
2、指定endblock
超出目标表的块的范围是一个错误。
3、默认情况下,所有的块都被检测。
4.1.2 verify_heapam 输出说明
- blkno
坏块的编号。 - offnum
损坏元组的OffsetNumber
。 - attnum
为损坏元组中损坏列的字段编号。 - msg
描述检测到的问题的消息。
4.2 语法定义
verify_heapam(relation regclass, on_error_stop boolean, check_toast boolean, skip text, startblock bigint, endblock bigint, blkno OUT bigint, offnum OUT integer, attnum OUT integer, msg OUT text) returns setof record
5 heapallindexed 参数详解
当 B-Tree
验证函数的 heapallindexed
参数为true时;
5.1 检查过程与原理
1、索引和对应的关联表执行一个额外的验证过程。
2、验证由一个“模拟”CREATE INDEX操作组成,创建一个临时的、内存中的模拟索引。
3、模拟索引对目标索引中的每一个元组“采集指纹”。
4、heapallindexed
验证背后的高层原则是:新建的索引与现有索引需要完全一致。
5.2 heapallindexed 参数对性能的影响
1、heapallindexed
阶段会增加明显的开销:验证的时间通常将会延长几倍。
2、模拟索引的大小以 maintenance_work_mem
为界。
3、为了确保不超过2%的失效概率能检测到不一致,每个元组需要大约2个字节的内存。
3、因为每个元组可用的内存变少,错失一处不一致的概率就会慢慢增加。
4、这种限制方法显著地限制了函数验证的开销,但仅仅略微降低了检测到问题的概率。
6 amechk 扩展模块适用场景
6.1 排序规则的更改
1、text
可排序类型数据的比较必须是不变的(正如用于B-树索引扫描的所有比较必须不变一样),这意味着操作系统排序规则必须保持不变。
2、操作系统排序规则的更新会导致索引失效。
3、Master 节点和 Standby 节点排序顺序的不一致也会导致索引失效。
(1)这可能是因为使用的主操作系统版本不一致。
(2)不一致通常仅出现在 Standby节点上,因此通常也仅能在后备服务器上检测到。
6.2 索引和表之间的结构不一致
6.2.1 损坏原因
1、表的块损坏会导致索引损坏。
2、访问方法代码、排序代码或者事务管理代码中未发现的BUG导致的损坏。
3、开发测试时引入逻辑不一致的新特性。
6.2.2 解决办法
4、进行标准回归测试时持续地调用amcheck函数。
6.3 存储故障
在没有开启校验和的文件系统或者存储子系统故障。
注意,如果在访问块时仅有一次共享缓存命中,验证时amcheck会在检查表示在某个共享内存缓冲区中的页面。因此,amcheck没有必要在验证时检查从文件系统读出的数据。
注意当校验和被启用时,如果一个损坏的块被读取到缓冲区中,amcheck可能会由于校验和失效而产生错误。
6.4 内存故障
PostgreSQL无法提供针对可更正内存错误的保护并且它假定用户使用的是具有工业标准纠错码(ECC)或更好保护技术的RAM。不过,ECC内存通常只能免疫单个位错误,并且不应该假定它能提供对导致内存损坏失效的绝对保护。
7 小结
在执行heapallindexed验证时,通常有大幅增加的机会可以检测单个位错误,因为会测试严格的二元等值并且会在堆中测试被索引属性。
由于有故障的存储硬件,或者相关文件被不相关的软件覆盖或修改,可能会发生结构损坏。 这类损坏也可以通过data page checksums来检测。
格式正确、内部一致并且相对于其内部校验和正确的关系页依然可能包含逻辑损坏。 因此,这类损坏不能被checksums所检测到。 例如包括主表中的toasted值在toast表中缺少相应的条目,以及主表中具有比数据库或集群中最古老的有效Transaction ID更旧的Transaction ID的元组。
在生产系统中已经观察到多个导致逻辑损坏的原因,包括PostgreSQL服务器软件中的缺陷、错误且考虑欠妥的备份和恢复工具,以及用户错误。
在实时生产环境中,表的损坏是最令人是最不欢迎的,并且风险极高。
基于此原因,设计了verify_heapam以在无过度风险的情况下诊断错误。
它不能保证防止后端崩溃的所有原因,因为在严重损坏的系统上,即使执行调用查询也可能不安全。执行对catalog tables的访问,如果编目自身损坏了,也可能会出现问题。
通常,amcheck仅能证明损坏的存在,但它无法证明损坏不存在。